/**
 * $RCSfile: NewsgroupGateway.java,v $
 * $Revision: 1.26 $
 * $Date: 2001/09/22 17:43:18 $
 *
 * Copyright (C) 1999-2001 CoolServlets Inc. All rights reserved.
 * ===================================================================
 * The Jive Software License (based on Apache Software License, Version 1.1)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by
 *        Jive Software (http://www.jivesoftware.com)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Jive" and "CoolServlets" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please
 *    contact webmaster@coolservlets.com.
 *
 * 5. Products derived from this software may not be called "Jive",
 *    nor may "Jive" appear in their name, without prior written
 *    permission of CoolServlets.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL COOLSERVLETS INC OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 * This software consists of voluntary contributions made by many
 * individuals on behalf of Jive Software. For more information
 * on Jive Software please visit http://www.jivesoftware.com.
 */

package com.jivesoftware.forum.gateway;

import java.io.*;
import java.util.*;
import java.text.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;
import com.jivesoftware.forum.*;
import com.jivesoftware.forum.model.ForumMessageNotFoundException;
import com.jivesoftware.forum.model.ForumNotFoundException;
import com.jivesoftware.forum.model.UnauthorizedException;
import com.jivesoftware.util.*;
import dog.mail.nntp.*;

/**
 * A gateway for the import and export of messages to and
 * from a newsgroup
 *
 * @author Bruce Ritchie
 */
public class NewsgroupGateway implements Gateway {

    private NNTPGateway gateway;
    private int lastMessageNumberSeen = -1;

    /**
     * used to flag messages in the forum with a message id
     * specific to this gateway
     */
    public static final String GATEWAY_MESSAGE_ID = "NNTP-Message-ID";

    /**
     * used to flag messages in the forum with a parent id
     * specific to this gateway
     */
    public static final String GATEWAY_PARENT_ID = "NNTP-Parent-ID";

    /**
     * Used for defining for the nntp gateway from when to do the import
     */
    private static final SimpleDateFormat DATEFORMATTER =
            new SimpleDateFormat("yyyy.MM.dd hh:mm:ss");

    /**
     * Create an instance
     */
    public NewsgroupGateway(ForumFactory factory, Forum forum) {
        gateway = new NNTPGateway(factory, forum);
    }

    //FROM THE GATEWAY INTERFACE//

    public synchronized void importData(Date afterDate) throws GatewayException
    {
        gateway.importData(afterDate);
    }

    /**
     * Export a forum message to a newsgroup.
     *<br>
     * NOTE: It is imperative that parent messages get exported prior to
     * children messages so that the proper message headers can be set. Failure
     * to do this will result in messages that may not be threaded properly
     * when viewed using a newsreader.
     *
     * @param forumMessage message to export
     * @throws GatewayException if a connection was unable to be established
     */
    public synchronized void exportData(ForumMessage forumMessage) throws GatewayException
    {
        gateway.exportData(forumMessage);
    }

    //GATEWAY PROPERTIES//

    public String getUsername() {
        return gateway.getUsername();
    }

    public void setUsername(String username) {
        if ("".equals(username)) {
            username = null;
        }
        gateway.setUsername(username);
    }

    public String getPassword() {
        return gateway.getPassword();
    }

    public void setPassword(String password) {
        if ("".equals(password)) {
            password = null;
        }
        gateway.setPassword(password);
    }

    /** default 119 */
    public int getNNTPPort() {
        return gateway.getPort();
    }

    public synchronized void setNNTPPort(int port) {
        gateway.setPort(port);

        // Old session no longer valid so set to null.
        gateway.session = null;
    }

    public String getNNTPHost() {
        return gateway.getHost();
    }

    public synchronized void setNNTPHost(String host) {
        gateway.setHost(host);

        // Old session no longer valid so set to null.
        gateway.session = null;
    }

     public String getNewsgroup() {
        return gateway.getMailbox();
    }

    public synchronized void setNewsgroup(String newsgroup) {
        gateway.setMailbox(newsgroup);

        // Old session no longer valid so set to null.
        gateway.session = null;
    }

    /**
     * Returns the body that will be used when creating temporary parent
     * messages. It's possible with email accounts and mailing
     * lists to get a response to a message before getting the original message.
     * If this happens and only the child message is available at the time of
     * the gateway import, Jive must still have a way to establish a correct
     * parent/child relationship. Therefore, a temporary fake parent message is
     * created using an extrapolated subject from the child, and a body with the
     * value <tt>temporaryParentBody</tt>. By default, this value will be
     * the empty String. However, you may wish to create a short message that
     * explains the nature of the fake parent message to the user.<p>
     *
     * On subsequent imports when a real parent message is found, the fake
     * data will be replaced with the correct subject and body.<p>
     *
     * @return the message body that will be used for temporary fake parent
     *      messages.
     */
    public String getTemporaryParentBody() {
        return gateway.getTemporaryParentBody();
    }

    /**
     * Sets the body that will be used when creating temporary parent
     * messages. It's possible with email accounts and mailing
     * lists to get a response to a message before getting the original message.
     * If this happens and only the child message is available at the time of
     * the gateway import, Jive must still have a way to establish a correct
     * parent/child relationship. Therefore, a temporary fake parent message is
     * created using an extrapolated subject from the child, and a body with the
     * value <tt>temporaryParentBody</tt>. By default, this value will be
     * the empty String. However, you may wish to create a short message that
     * explains the nature of the fake parent message to the user.<p>
     *
     * On subsequent imports when a real parent message is found, the fake
     * data will be replaced with the correct subject and body.<p>
     *
     * @param temporaryParentBody the message body that will be used for
     *      temporary fake parent messages.
     */
    public void setTemporaryParentBody(String temporaryParentBody) {
        gateway.setTemporaryParentBody(temporaryParentBody);
    }

    /**
     * Returns true if debugging is turned on for the NNTP transport layer.
     * Debug information is written to <tt>System.out.err</tt>.
     *
     * @return true if NNTP debugging is turned on.
     */
    public boolean isDebugEnabled() {
        return gateway.isDebugEnabled();
    }

    /**
     * Toggles NNTP transport layer debugging on or off. Debug information is
     * written to <tt>System.out</tt>.
     *
     * @param debug true if NNTP debugging should be enabled.
     */
    public synchronized void setDebugEnabled(boolean debugEnabled) {
        gateway.setDebugEnabled(debugEnabled);

        // Old session no longer valid so set to null.
        gateway.session = null;
    }

    /**
     * True if a user's privacy setting on their email address should be obeyed
     * when doing an export.
     */
    public boolean isEmailPrefEnabled() {
        return gateway.isEmailPrefEnabled();
    }

    public void setEmailPrefEnabled(boolean enabled) {
        gateway.setEmailPrefEnabled(enabled);
    }

    public String getDefaultFromAddress() {
        return gateway.getDefaultFromAddress();
    }

    public void setDefaultFromAddress(String address) {
        if ("".equals(address)) {
            address = null;
        }
        gateway.setDefaultFromAddress(address);
    }

    /**
     * Retrieves the Organization header for outbound
     * nntp messages. Default is null.
     *
     * @return the current organization header
     */
    public String getOrganization() {
        return gateway.getOrganization();
    }

    /**
     * Sets the Organization header for outbound
     * nntp messages
     *
     * @param organization the string to set the
     *   organization header to
     */
    public void setOrganization(String organization) {
        if (organization != null) {
            gateway.setOrganization(organization);
        }
    }

    public void setLastMessageNumberSeen(int messageNumber) {
        if (messageNumber > 0) {
            lastMessageNumberSeen = messageNumber;
        }
    }

    public int getLastMessageNumberSeen() {
        return lastMessageNumberSeen;
    }

    /**
     * An extension of the JavaMailGateway class
     */
    private class NNTPGateway extends JavaMailGateway {

        protected Session session = null;
        protected String organization = null;

        public String getOrganization() {
            return organization;
        }

        public void setOrganization(String organization) {
            this.organization = organization;
        }

        public NNTPGateway(ForumFactory factory, Forum forum) {
            super(factory, forum);
            setPort(119); // use the default port of 119
            setProtocol("nntp");

            gatewayMessageId = GATEWAY_MESSAGE_ID;
            gatewayParentId  = GATEWAY_PARENT_ID;
        }

        public void exportData(ForumMessage forumMessage) throws GatewayException {

           // check to make sure that we have been properly setup
            if (host == null || mailbox == null || defaultFromAddress == null) {
                throw new GatewayException("Required properties are not all set.");
            }

            // refuse to re-export a message which has previously been exported
            // or imported or which was automatically created for threading
            // purposes by an import gateway
            if (forumMessage.getProperty(GATEWAY_MESSAGE_ID) != null ||
                    forumMessage.getProperty(DUMMY_PARENT_HEADER) != null)
            {
                return;
            }

            try {
                // Create the session if necessary.
                if (session == null) {
                    session = Session.getDefaultInstance(new Properties(), null);
                    session.setDebug(debugEnabled);
                }

                InternetAddress fromAddress = getFromAddress(forumMessage);
                MimeMessage message = new MimeMessage(session);

                message.setFrom(fromAddress);
                message.setRecipient(MimeMessage.RecipientType.NEWSGROUPS,
                    new NewsAddress(mailbox));
                // The body is the message + the footer.
                StringBuffer body = new StringBuffer(forumMessage.getUnfilteredBody());
                Forum forum = gateway.factory.getForum(gateway.forumID);
                GatewayManager manager = forum.getGatewayManager();
                body.append(manager.getTranslatedFooter(forumMessage));
                message.setContent(body.toString(), "text/plain");
                message.setSubject(forumMessage.getSubject());

                if (organization != null) {
                    message.setHeader("Organization", organization);
                }

                // Attempt to get a parent message id so we can set
                // the references header properly for threading to work
                // good netique :)
                try {
                    ForumThread thread = forumMessage.getForumThread();
                    TreeWalker tree = thread.treeWalker();
                    ForumMessage parent = tree.getParent(forumMessage);
                    String parentId = parent.getProperty(GATEWAY_MESSAGE_ID);
                    if (parentId != null && !parentId.equals("")) {
                        message.setHeader("References", parentId);
                    }
                }
                catch (ForumMessageNotFoundException fmnfe) { /* ignore */ }

                // save all the changes to the message
                message.saveChanges();

                URLName url = new URLName(protocol, host, port, mailbox,
                        username, password);
                NNTPTransport trans = new NNTPTransport(session, url);

                trans.connect(host, port, username, password);
                trans.sendMessage(message, message.getRecipients(
                        MimeMessage.RecipientType.NEWSGROUPS));
                // set the message ID so that we don't inadvertently cause a
                // loop to happen
                forumMessage.setProperty(GATEWAY_MESSAGE_ID, message.getMessageID());
            }
            catch (Exception e) {
                throw new GatewayException(e);
            }
        }

        /**
         * override base class to set a system property and then
         * get the JavaMail store
         *
         * @return Store     a connected store object
         * @param afterDate the date after which we'll import a message
         * @param store a JavaMail store object
         * @throws MessagingException if error occurred establishing the connection
         */
        protected Store getStore(Date afterDate) throws MessagingException {
            if (afterDate == null) {
                afterDate = new Date(0L);
            }

            Properties props = new Properties();
            props.put("mail.nntp.useNewNews", "false");
            props.put("mail.nntp.SimpleDateFormat", DATEFORMATTER.toPattern());
            props.put("mail.nntp.afterDate", DATEFORMATTER.format(afterDate));
            props.put("mail.nntp.memoryOptimisation", Boolean.TRUE.toString());
            props.put("mail.nntp.checkNewTimeout", "10");
            props.put("mail.store.protocol", "nntp");
            props.put("mail.transport.protocol", "nntp-post");
            if (lastMessageNumberSeen > 0) {
                props.put("mail.nntp.lastArticleIdSeen", String.valueOf(lastMessageNumberSeen));
            }

            URLName url = new URLName(protocol, host, port, mailbox, username, password);

            session = Session.getInstance(props, null);
            session.setDebug(debugEnabled);
            Store store = new NNTPStore(session, url);
            store.connect();

            return store;
        }

        /**
         * Overridden so that we can grab the article number from the provider.
         */
        protected String getMessageID(Message message) throws MessagingException {
            if (message.getMessageNumber() > 0) {
                int messageNumber = message.getMessageNumber();
                if (messageNumber != lastMessageNumberSeen) {
                    lastMessageNumberSeen = messageNumber;
                }
            }

            return super.getMessageID(message);
        }

        /**
         * Overrideen so that we save the latest article number for the next
         * run.
         */
        protected void cleanup() {
            try {
                GatewayManager gatewayManager = factory.getForum(forumID).getGatewayManager();

                for (int i=0; i < gatewayManager.getGatewayCount(); i++) {
                    Gateway gateway = gatewayManager.getGateway(i);
                    if (gateway != null && gateway instanceof NewsgroupGateway) {
                        ((NewsgroupGateway) gateway).setLastMessageNumberSeen(lastMessageNumberSeen);
                    }
                }

                gatewayManager.saveGateways(false);
            }
            catch (ForumNotFoundException e) { /* do nothing */ }
            catch (UnauthorizedException e) { /* do nothing */ }
        }

        /**
         * Determine the "from" address, which will normally be
         * the message author's name and email address. However, we have to
         * use a default address in some circumstances when that info isn't
         * available.
         *
         * @param forumMessage the message for which we are trying to extract
         *                     the from address
         * @return InternetAddress the from address
         */
        private InternetAddress getFromAddress(ForumMessage forumMessage) {

            InternetAddress fromAddress = null;

            try {
                if (!forumMessage.isAnonymous()) {
                    String user = forumMessage.getUser();
                    Map<String, String> userInfo = ForumFactory.getUserInfoCenter().getUerInfo(user);
                    String name, email = null;
                    // Get the name, or use their username if the name is hidden.
                    if (Boolean.parseBoolean(userInfo.get(UserInfoCenter.NAME_VISIABLE))) {
                        name = userInfo.get(UserInfoCenter.NAME);
                    }
                    else {
                        name = userInfo.get(UserInfoCenter.USER_NAME);
                    }
                    // Get their email address, or use the dummy address if their
                    // email is hidden and the admin hasn't overriden that.
                    if (!Boolean.parseBoolean(UserInfoCenter.EMAIL_VISIABLE) && emailPrefEnabled) {
                        email = defaultFromAddress;
                    }
                    else {
                        email = userInfo.get(UserInfoCenter.EMAIL);
                    }
                    fromAddress = new InternetAddress(email, name);
                }
                // Message was posted anonymously. However, a name and email might
                // be defined through extended properties.
                else if (forumMessage.getProperty("name") != null &&
                         forumMessage.getProperty("email") != null)
                {
                    String name  = forumMessage.getProperty("name");
                    String email = forumMessage.getProperty("email");
                    fromAddress = new InternetAddress(email, name);
                }
                // Getting "from" address failed. Use the default address.
                else {
                    fromAddress = new InternetAddress(defaultFromAddress);
                }
            }
            catch (UnsupportedEncodingException e) {
                fromAddress = null;
            }
            catch (AddressException e) {
                fromAddress = null;
            }

            return fromAddress;
        }
    }
}